using System;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using Velocity4Net.Cfg;
using Velocity4Net.Errors;
using Velocity4Net.Util;

namespace Velocity4Net.Runtime.ResourceManage.Loader
{
    /**
     * A loader for templates stored on the file system.  Treats the template
     * as relative to the configured root path.  If the root path is empty
     * treats the template name as an absolute path.
     *
     * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a>
     * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a>
     * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
     * @version $Id: FileResourceLoader.java 743616 2009-02-12 04:38:53Z nbubna $
     */
    public class FileResourceLoader : ResourceLoader
    {
        /**
         * The paths to search for templates.
         */
        private List<object> paths = new List<object>();

        /**
         * Used to map the path that a template was found on
         * so that we can properly check the modification
         * times of the files. This is synchronizedMap
         * instance.
         */
        private IDictionary templatePaths = new Hashtable();

        /** Shall we inspect unicode files to see what encoding they contain?. */
        private bool unicode = false;

        /**
         * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#init(org.apache.commons.collections.ExtendedProperties)
         */
        public override void Init(VelocitySettings configuration)
        {
            if (log.isTraceEnabled())
            {
                log.trace("FileResourceLoader : initialization starting.");
            }

            paths.AddRange(configuration.GetVector("path"));

            // unicode files may have a BOM marker at the start, but Java
            // has problems recognizing the UTF-8 bom. Enabling unicode will
            // recognize all unicode boms.
            unicode = configuration.GetBoolean("unicode", false);

            if (log.isDebugEnabled())
            {
                log.debug("Do unicode file recognition:  " + unicode);
            }

            if (log.isDebugEnabled())
            {
                // trim spaces from all paths
                //StringUtils.trimStrings(paths);

                // this section lets tell people what paths we will be using
                int sz = paths.Count;
                for (int i = 0; i < sz; i++)
                {
                    log.debug("FileResourceLoader : adding path '" + (String)paths[i] + "'");
                }
                log.trace("FileResourceLoader : initialization complete.");
            }
        }

        /**
         * Get an InputStream so that the Runtime can build a
         * template with it.
         *
         * @param templateName name of template to get
         * @return InputStream containing the template
         * @throws ResourceNotFoundException if template not found
         *         in the file template path.
         */
        public override Stream GetResourceStream(String templateName)
        {
            /*
             * Make sure we have a valid templateName.
             */
            if (string.IsNullOrEmpty(templateName))
            {
                /*
                 * If we don't get a properly formed templateName then
                 * there's not much we can do. So we'll forget about
                 * trying to search any more paths for the template.
                 */
                throw new ResourceNotFoundException(
                    "Need to specify a file name or file path!");
            }

            String template = StringUtils.normalizePath(templateName);
            if (template == null || template.Length == 0)
            {
                String msg = "File resource error : argument " + template +
                    " contains .. and may be trying to access " +
                    "content outside of template root.  Rejected.";

                log.error("FileResourceLoader : " + msg);

                throw new ResourceNotFoundException(msg);
            }

            int size = paths.Count;
            for (int i = 0; i < size; i++)
            {
                String path = (String)paths[i];
                Stream inputStream = null;

                try
                {
                    inputStream = findTemplate(path, template);
                }
                catch (IOException ioe)
                {
                    String msg = "Exception while loading Template " + template;
                    log.error(msg, ioe);
                    throw new VelocityException(msg, ioe);
                }

                if (inputStream != null)
                {
                    /*
                     * Store the path that this template came
                     * from so that we can check its modification
                     * time.
                     */
                    
                    templatePaths[templateName]= path;
                    return inputStream;
                }
            }

            /*
             * We have now searched all the paths for
             * templates and we didn't find anything so
             * throw an exception.
             */
            throw new ResourceNotFoundException("FileResourceLoader : cannot find " + template);
        }

        /**
         * Overrides superclass for better performance.
         * @since 1.6
         */
        public bool resourceExists(String name)
        {
            if (name == null)
            {
                return false;
            }
            name = StringUtils.normalizePath(name);
            if (name == null || name.Length == 0)
            {
                return false;
            }

            int size = paths.Count;
            for (int i = 0; i < size; i++)
            {
                String path = (String)paths[i];
                try
                {
                    FileInfo file = getFile(path, name);
                    if (file.Exists)
                    {
                        return true;
                    }
                }
                catch (System.Exception ioe)
                {
                    String msg = "Exception while checking for template " + name;
                    log.debug(msg, ioe);
                }
            }
            return false;
        }

        /**
         * Try to find a template given a normalized path.
         *
         * @param path a normalized path
         * @param template name of template to find
         * @return InputStream input stream that will be parsed
         *
         */
        private Stream findTemplate(String path, String template)
        {
            try
            {
                FileInfo file = getFile(path, template);
                FileStream stream = file.OpenRead();


                return stream;
            }
            catch (System.Exception fnfe)
            {
                /*
                 *  log and convert to a general Velocity ResourceNotFoundException
                 */
                return null;
            }
        }

        private void closeQuiet(Stream _is)
        {
            if (_is != null)
            {
                try
                {
                    _is.Close();
                }
                catch (IOException ioe)
                {
                    // Ignore
                }
            }
        }

        /**
         * How to keep track of all the modified times
         * across the paths.  Note that a file might have
         * appeared in a directory which is earlier in the
         * path; so we should search the path and see if
         * the file we find that way is the same as the one
         * that we have cached.
         * @param resource
         * @return True if the source has been modified.
         */
        public override bool isSourceModified(Resource resource)
        {
            /*
             * we assume that the file needs to be reloaded;
             * if we find the original file and it's unchanged,
             * then we'll flip this.
             */
            bool modified = true;

            String fileName = resource.Name;
            String path = (String)templatePaths[fileName];
            FileInfo currentFile = null;

            for (int i = 0; currentFile == null && i < paths.Count; i++)
            {
                String testPath = (String)paths[i];
                FileInfo testFile = getFile(testPath, fileName);
                if (testFile.Exists)
                {
                    currentFile = testFile;
                }
            }
            FileInfo file = getFile(path, fileName);
            if (currentFile == null || !file.Exists)
            {
                /*
                 * noop: if the file is missing now (either the cached
                 * file is gone, or the file can no longer be found)
                 * then we leave modified alone (it's set to true); a
                 * reload attempt will be done, which will either use
                 * a new template or fail with an appropriate message
                 * about how the file couldn't be found.
                 */
            }
            else if (currentFile.FullName==file.FullName && file.Exists)
            {
                /*
                 * if only if currentFile is the same as file and
                 * file.lastModified() is the same as
                 * resource.getLastModified(), then we should use the
                 * cached version.
                 */
                modified = (file.LastWriteTime.Ticks != resource.LastModified);
            }

            /*
             * rsvc.debug("isSourceModified for " + fileName + ": " + modified);
             */
            return modified;
        }

        /**
         * @see org.apache.velocity.runtime.resource.loader.ResourceLoader#getLastModified(org.apache.velocity.runtime.resource.Resource)
         */
        public override long getLastModified(Resource resource)
        {
            String path = (String)templatePaths[resource.Name];
            FileInfo file = getFile(path, resource.Name);

            if (file.Exists)
            {
                return file.LastWriteTime.Ticks;
            }
            else
            {
                return 0;
            }
        }


        /**
         * Create a File based on either a relative path if given, or absolute path otherwise
         */
        private FileInfo getFile(String path, String template)
        {

            FileInfo file = null;

            if ("".Equals(path))
            {
                file = new FileInfo(template);
            }
            else
            {
                /*
                 *  if a / leads off, then just nip that :)
                 */
                if (template.StartsWith("/"))
                {
                    template = template.Substring(1);
                }

                file = new FileInfo(Path.Combine(path, template));
            }

            return file;
        }
    }
}
